package com.binance.assetservice.web.service.impl;

import com.binance.assetservice.data.mapper.UserAssetLogMapper;
import com.binance.assetservice.data.models.UserAssetItem;
import com.binance.assetservice.data.models.UserAssetLogItem;
import com.binance.assetservice.web.service.IUserAssetLogService;
import com.binance.assetservice.web.utils.Constants;
import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryListener;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import lombok.extern.slf4j.Slf4j;
import org.javasimon.aop.Monitored;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Monitored
@Service(IUserAssetLogService.BEAN_ID)
@Slf4j
public class UserAssetLogServiceImpl implements IUserAssetLogService {

    @Autowired
    private UserAssetLogMapper userAssetLogMapper;

    private static final int MAX_RETRY = 20;
    private static final int SLEEP_TIME = 1;

    private RetryListener listener = new RetryListener() {

        @Override
        public <V> void onRetry(Attempt<V> attempt) {
            if (attempt.hasException()) {
                log.warn(String.format("%d retry after %d ms", attempt.getAttemptNumber(),
                        attempt.getDelaySinceFirstAttempt()), attempt.getExceptionCause());
                if (attempt.getExceptionCause().getCause().getMessage().contains(
                        "The MySQL server is running with the --read-only option so it cannot execute this statement")) {
                    log.error("Failed to point to new master during the failover, exiting jvm",
                            attempt.getExceptionCause());
                    System.exit(-1);
                }
            }
        }

    };
    private final Retryer<Integer> retryer =
            RetryerBuilder.<Integer>newBuilder().retryIfExceptionOfType(Throwable.class).withRetryListener(listener)
                    .withWaitStrategy(WaitStrategies.fixedWait(SLEEP_TIME, TimeUnit.SECONDS))
                    .withStopStrategy(StopStrategies.stopAfterAttempt(MAX_RETRY)).build();

    @Override
    public void addAssetLog(Long tranId, Integer type, String asset, String userId, BigDecimal assetDelta,
            String assetInfo, UserAssetItem userAsset) {
        this.addAssetLog(userAssetLogItem -> {
            userAssetLogItem.setUid(userId);
            userAssetLogItem.setAsset(asset);
            userAssetLogItem.setTranId(tranId);
            userAssetLogItem.setType(type != null ? type : Constants.ASSET_TRADING);
            userAssetLogItem.setDelta(assetDelta);
            userAssetLogItem.setFree(userAsset.getFree());
            userAssetLogItem.setLocked(userAsset.getLocked());
            userAssetLogItem.setFreeze(userAsset.getFreeze());
            userAssetLogItem.setWithdrawing(userAsset.getWithdrawing());
            userAssetLogItem.setIpoing(userAsset.getIpoing());
            userAssetLogItem.setIpoable(userAsset.getIpoable());
            userAssetLogItem.setStorage(userAsset.getStorage());
            userAssetLogItem.setInfo(assetInfo);
            userAssetLogItem.setTime(new Date());
        });
    }

    @Override
    public void addAssetLog(Consumer<UserAssetLogItem> consumer) {
        UserAssetLogItem userAssetLogItem = new UserAssetLogItem();
        consumer.accept(userAssetLogItem);
        try {
            retryer.call(() -> this.userAssetLogMapper.insertUsreAssetLog(userAssetLogItem));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public BigDecimal getAssetAmountByTime(Long userId, String asset, Date endTime) {
        BigDecimal delta = userAssetLogMapper.getAssetDeltaByTime(userId.toString(), asset, endTime);
        BigDecimal deltaSum = userAssetLogMapper.getAssetDelta(userId.toString(), asset);
        if (delta == null){
            delta = BigDecimal.ZERO;
        }
        if (deltaSum == null){
            deltaSum = BigDecimal.ZERO;
        }
        return delta.add(deltaSum);
    }

}
